package com.mdp.tpa.pay.paypal.ctrl;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mdp.tpa.pay.paypal.config.PaypalConfig;
import com.mdp.tpa.pay.paypal.entity.PaypalPaymentIntent;
import com.mdp.tpa.pay.paypal.entity.PaypalPaymentMethod;
import com.mdp.tpa.pay.paypal.service.PaypalService;

import com.mdp.tpa.pay.paypal.util.URLUtils;
import com.paypal.api.payments.CreditCard;
import com.paypal.api.payments.Event;
import com.paypal.api.payments.Links;
import com.paypal.api.payments.Payment;
import com.paypal.base.Constants;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import static com.mdp.core.utils.BaseUtils.map;

@Controller
@RequestMapping("/**/pay/paypal")
public class PaymentController {
    @Autowired
    private APIContext apiContext;
    @Autowired
    private PaypalConfig paypalConfig;
    @Autowired
    private PaypalService paypalService;
    private ConcurrentHashMap<String, String> orderPaymMap = new ConcurrentHashMap();// orderId与paymentId的对应关系
    private Logger log = LoggerFactory.getLogger(getClass());

    /**
     * 首页
     */
    @RequestMapping(method = RequestMethod.GET)
    public String index() {
        return "index";
    }

    /**
     * 创建订单请求
     * 创建成功返回payment对象，保存本地OrderId和paymentId对应关系
     */
    @RequestMapping(method = RequestMethod.POST, value = "uniOrder")
    public String pay(HttpServletRequest request) {
        log.info("=========================================================================================");
        String orderId = "2020110200001";// 本地系统Id
        String cancelUrl = URLUtils.getBaseURl(request) + "/" + PaypalConfig.CANCEL_URL + "?orderId=" + orderId;// http://localhost:8080/pay/cancel
        String successUrl = URLUtils.getBaseURl(request) + "/" + PaypalConfig.SUCCESS_URL;
        try {
            //调用交易方法
            Payment payment = paypalService.createPayment(
                    orderId,
                    300.00,
                    "USD",
                    PaypalPaymentMethod.paypal,
                    PaypalPaymentIntent.authorize,
                    "这是一笔300美元的交易",
                    cancelUrl,
                    successUrl);
            for (Links links : payment.getLinks()) {
                if (links.getRel().equals("approval_url")) {
                    // 客户付款登陆地址【判断币种CNY无法交易】
                    String paymentId = payment.getId();
                    orderPaymMap.put(orderId, paymentId);// 保存本地OrderId和paymentId对应关系
                    log.info("创建支付订单返回paymentId : " + paymentId);
                    log.info("支付订单状态state : " + payment.getState());
                    log.info("支付订单创建时间create_time : " + payment.getCreateTime());
                    log.info("=========================================================================================");
                    return "redirect:" + links.getHref();
                }
            }
        } catch (PayPalRESTException e) {
            log.error(e.getMessage());// 支付失败【使用CNY】
        }
        log.info("=========================================================================================");
        return "redirect:/";
    }

    /**
     * 失败回调
     *   触发回调情景：
     *     1、用户在支付页面点击取消支付
     *     2、用户支付成功后点击网页回退，再点击返回商家按钮触发
     *   判断是否用户主动取消付款逻辑：
     *     1、设置回调地址的时候拼接本地订单ID：?orderId=XXXX
     *     2、然后根据orderId查询paymentId，继而调用sdk查询订单支付状态
     *      * http://localhost:8080/pay/cancel?orderId=2020110200001&token=EC-70674489LL9806126
     */
    @RequestMapping(method = RequestMethod.GET, value = PaypalConfig.CANCEL_URL)
    public String cancelPay(@RequestParam("token") String token, @RequestParam("orderId") String orderId) {
        try {
            String paymentId = orderPaymMap.get(orderId);
            Payment payment = Payment.get(apiContext, paymentId);
            String state = payment.getState();
            log.info("交易取消回调：支付订单状态：{} ", state);
            if (state.equals("approved")) {// 已支付
                return "success";
            }
        } catch (PayPalRESTException e) {
            e.printStackTrace();
        }
        return "cancel";
    }

    /**
     * 成功回调 + 支付 + webhook方式
     *    买家确认付款，执行支付并直接返回通知
     */
    @RequestMapping(method = RequestMethod.POST, value = PaypalConfig.SUCCESS_URL)
    public String successPay(HttpServletRequest req, HttpServletResponse resp) {
        log.info("=========================================================================================");
        try {
            String body=getBody(req);
            Boolean result = Event.validateReceivedEvent(apiContext, getHeadersInfo(
                    req), body);
             log.info("Webhook Validated:  " + result);
             log.info("data:{}",body);
            JSONObject data=JSONObject.parseObject(body);
             String eventType= (String) data.get("event_type");
             if("CHECKOUT.ORDER.APPROVED".equals(eventType)){
                 JSONObject resource=data.getJSONObject("resource");
                 JSONArray purchaseUnits=resource.getJSONArray("purchase_units");
                 if(purchaseUnits!=null && purchaseUnits.size()>0){
                     JSONObject item=purchaseUnits.getJSONObject(0);
                     String invoiceId=item.getString("invoice_id");//订单编号
                     if(StringUtils.hasText(invoiceId)){
                         log.info("invoice_id:{}",invoiceId);
                     }
                 }
             }
        } catch (PayPalRESTException e) {
            log.error(e.getMessage());
        } catch (InvalidKeyException e) {
            log.error(e.getMessage());
        } catch (
        NoSuchAlgorithmException e) {
            log.error(e.getMessage());
        } catch (
        SignatureException e) {
            log.error(e.getMessage());
        } catch (
                IOException e) {
            log.error(e.getMessage());
        }
        return "success";
    }

    public static void main(String[] args) {
        String body="{\"id\":\"WH-4ER20894EM5385045-0TW95383R4373523M\",\"event_version\":\"1.0\",\"create_time\":\"2023-03-20T15:49:12.104Z\",\"resource_type\":\"checkout-order\",\"resource_version\":\"2.0\",\"event_type\":\"CHECKOUT.ORDER.APPROVED\",\"summary\":\"An order has been approved by buyer\",\"resource\":{\"create_time\":\"2023-03-20T15:49:00Z\",\"purchase_units\":[{\"reference_id\":\"default\",\"amount\":{\"currency_code\":\"USD\",\"value\":\"0.01\"},\"payee\":{\"email_address\":\"sb-slq43r25217391@business.example.com\",\"merchant_id\":\"7FG6Y2E3J9X56\"},\"custom_id\":\"U2KD4BI6138\",\"invoice_id\":\"CM16792941618899\",\"shipping\":{\"name\":{\"full_name\":\"Doe John\"},\"address\":{\"address_line_1\":\"NO 1 Nan Jin Road\",\"admin_area_2\":\"Shanghai\",\"admin_area_1\":\"Shanghai\",\"postal_code\":\"200000\",\"country_code\":\"C2\"}}}],\"links\":[{\"href\":\"https://api.sandbox.paypal.com/v2/checkout/orders/1U57764479733261U\",\"rel\":\"self\",\"method\":\"GET\"},{\"href\":\"https://api.sandbox.paypal.com/v2/checkout/orders/1U57764479733261U\",\"rel\":\"update\",\"method\":\"PATCH\"},{\"href\":\"https://api.sandbox.paypal.com/v2/checkout/orders/1U57764479733261U/capture\",\"rel\":\"capture\",\"method\":\"POST\"}],\"id\":\"1U57764479733261U\",\"payment_source\":{\"paypal\":{}},\"intent\":\"CAPTURE\",\"payer\":{\"name\":{\"given_name\":\"John\",\"surname\":\"Doe\"},\"email_address\":\"sb-r0tt025217370@personal.example.com\",\"payer_id\":\"D788J2LK55TEA\",\"address\":{\"country_code\":\"C2\"}},\"status\":\"APPROVED\"},\"links\":[{\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4ER20894EM5385045-0TW95383R4373523M\",\"rel\":\"self\",\"method\":\"GET\"},{\"href\":\"https://api.sandbox.paypal.com/v1/notifications/webhooks-events/WH-4ER20894EM5385045-0TW95383R4373523M/resend\",\"rel\":\"resend\",\"method\":\"POST\"}]}";
        JSONObject data=JSONObject.parseObject(body);
        String eventType= (String) data.get("event_type");
        if("CHECKOUT.ORDER.APPROVED".equals(eventType)){
            JSONObject resource=data.getJSONObject("resource");
            JSONArray purchaseUnits=resource.getJSONArray("purchase_units");
            if(purchaseUnits!=null && purchaseUnits.size()>0){
                JSONObject item=purchaseUnits.getJSONObject(0);
                String invoiceId=item.getString("invoice_id");//订单编号
                if(StringUtils.hasText(invoiceId)){
                 }
            }
        }
    }
    // Simple helper method to help you extract the headers from HttpServletRequest object.
    private static Map < String, String > getHeadersInfo(HttpServletRequest request) {
        Map < String, String > map = new HashMap< String, String >();
        @SuppressWarnings("rawtypes")
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            map.put(key, value);
        }
        return map;
    }
    // Simple helper method to fetch request data as a string from HttpServletRequest object.
    private static String getBody(HttpServletRequest request) throws IOException {
        String body;
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = null;
        try {
            InputStream inputStream = request.getInputStream();
            if (inputStream != null) {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                char[] charBuffer = new char[128];
                int bytesRead = -1;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } else {
                stringBuilder.append("");
            }
        } catch (IOException ex) {
            throw ex;
        } finally {
            if (bufferedReader != null) {
                try {
                    bufferedReader.close();
                } catch (IOException ex) {
                    throw ex;
                }
            }
        }
        body = stringBuilder.toString();
        return body;
    }

    /**
     * 成功回调 + 支付 + PDT同步通知
     *    买家确认付款，执行支付并直接返回通知
     */
    @RequestMapping(method = RequestMethod.GET, value = PaypalConfig.SUCCESS_URL)
    public String successPay(@RequestParam("paymentId") String paymentId, @RequestParam("PayerID") String payerId, @RequestParam("token") String token) {
        log.info("=========================================================================================");
        try {
            Payment payment = paypalService.executePayment(paymentId, payerId);
            if (payment.getState().equals("approved")) {
                String id = "";     // 交易ID，transactionId
                String state = "";  // 交易订单状态
                String time = "";   // 交易时间
                String custom = ""; // 本地OrderId
                if (payment.getIntent().equals(PaypalPaymentIntent.authorize.toString())) {
                    id = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getId();
                    state = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getState();
                    time = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getCreateTime();
                    custom = payment.getTransactions().get(0).getCustom();
                } else if (payment.getIntent().equals(PaypalPaymentIntent.sale.toString())) {
                    id = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getId();
                    state = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getState();
                    time = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getCreateTime();
                    custom = payment.getTransactions().get(0).getCustom();
                } else if (payment.getIntent().equals(PaypalPaymentIntent.order.toString())) {
                    id = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getId();
                    state = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getState();
                    time = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getCreateTime();
                    custom = payment.getTransactions().get(0).getCustom();
                }
                log.info("PDT通知：交易成功回调");
                log.info("付款人账户：" + payment.getPayer().getPayerInfo().getEmail());
                log.info("支付订单Id {}", paymentId);
                log.info("支付订单状态state : " + payment.getState());
                log.info("交易订单Id：{}", id);
                log.info("交易订单状态state : " + state);
                log.info("交易订单支付时间：" + time);
                log.info("本地系统OrderId：{}", custom);
                log.info("=========================================================================================");
                return "success";
            }
        } catch (PayPalRESTException e) {
            // 如果同步通知返回异常，可根据paymentId 来查询刷新订单状态
            // 同时IPN异步通知也可以更新订单状态
            log.error(e.getMessage());
        }
        return "redirect:/";
    }


    /**
     * IPN异步通知
     *   触发情景：
     *      1、买家支付成功
     *      2、卖家确认收取授权或订单款项
     *      3、卖家发放退款
     */
    @RequestMapping(method = RequestMethod.POST, value = "/notificationIPN")
    public void receivePaypalStatus(HttpServletRequest request, HttpServletResponse response) throws Exception {
        log.info("=========================================================================================");
        log.info("IPN通知：交易成功异步回调");
        PrintWriter out = response.getWriter();
        try {
            Enumeration<String> en = request.getParameterNames();
            /**
             * 修改订单状态
             *      保存失败则不验签，继续接受paypal异步回调直至保存成功【或者用MQ】
             */
            String paymentStatus = request.getParameter("payment_status").toUpperCase();  // 交易状态
            String paymentDate = request.getParameter("payment_date");      // 交易时间
            String custom = request.getParameter("custom");                 // 本地系统订单ID
            String auth_id = request.getParameter("auth_id");               // transactionId
            String txnId = request.getParameter("txn_id");                  // 当前回调数据id【具体逻辑查看 .md文档】
            String parentTxnId = request.getParameter("parent_txn_id");     // 父id
            String receiverEmail = request.getParameter("receiver_email");  // 收款人email
            String receiverId = request.getParameter("receiver_id");        // 收款人id
            String payerEmail = request.getParameter("payer_email");        // 付款人email
            String payerId = request.getParameter("payer_id");              // 付款人id
            String mcGross = request.getParameter("mc_gross");              // 交易金额
            String item_name = request.getParameter("item_name");
            log.info("paymentStatus = " + paymentStatus);
            log.info("txnId = " + txnId);
            log.info("parentTxnId = " + parentTxnId);
            log.info("authId（transactionId）= " + auth_id);
            log.info("custom（orderId）= " + custom);
            log.info("item_name= " + item_name);
            /**
             * 验证
             *   作用：
             *     订单状态修改成功，告诉paypal停止回调
             *   实现：
             *     在原参数的基础上加cmd=_notify-validate，然后对https://www.sandbox.paypal.com/cgi-bin/webscr发起POST验证请求
             */
            String str = "cmd=_notify-validate";
            while (en.hasMoreElements()) {
                String paramName = en.nextElement();
                String paramValue = request.getParameter(paramName);
                //此处的编码一定要和自己的网站编码一致，不然会出现乱码，paypal回复的通知为"INVALID"
                str = str + "&" + paramName + "=" + URLEncoder.encode(paramValue, "utf-8");
            }
            log.info("paypal传递过来的交易信息:" + str);// 建议在此将接受到的信息 str 记录到日志文件中以确认是否收到 IPN 信息
            URL url = new URL(paypalConfig.getWebscr());
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");//设置 HTTP 的头信息
            PrintWriter pw = new PrintWriter(connection.getOutputStream());
            pw.println(str);
            pw.close();
            /**
             * 回复
             *    接受PayPal对验证的回复信息
             */
            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String resp = in.readLine();
            in.close();
            resp = StringUtils.isEmpty(resp) ? "0" : resp;
            log.info("resp = " + resp);
            /**
             * 验证返回状态
             */
            if (PaypalConfig.PAYMENT_IPN_VERIFIED.equalsIgnoreCase(resp)) {
                /**
                 * 修改订单状态
                 *      根据订单状态paymentStatus确定当前回调的类型
                 */
                switch (paymentStatus) {
                    case PaypalConfig.PAYMENT_STATUS_PENDING:
                        // 商家待领取状态
                        break;
                    case PaypalConfig.PAYMENT_STATUS_VOIDED:
                        // 商家作废（30天以内，且必须是授权付款类型 或 订单付款类型），款项原路返回买家
                        break;
                    case PaypalConfig.PAYMENT_STATUS_COMPLETED:
                        // 商家领取
                        String captureId = txnId;   // 实际领取对象ID【授权付款 和 订单付款需要商家领取】
                        break;
                    case PaypalConfig.PAYMENT_STATUS_REFUNDED:
                        // 商家退款，需扣除费用
                        String refundId = txnId;
                        String captureId2 = parentTxnId;
                        break;
                }
            } else if (PaypalConfig.PAYMENT_IPN_INVALID.equalsIgnoreCase(resp)) {
                // 非法信息，可以将此记录到您的日志文件中以备调查
                log.error("IPN通知返回状态非法，请联系管理员，请求参数：" + str);
                log.error("Class: " + this.getClass().getName() + " method: " + Thread.currentThread().getStackTrace()[1].getMethodName());
                out.println("confirmError");
            } else {// 处理其他错误
                log.error("IPN通知返回状态非法，请联系管理员，请求参数：" + str);
                log.error("Class: " + this.getClass().getName() + " method: " + Thread.currentThread().getStackTrace()[1].getMethodName());
                out.println("confirmError");
            }
        } catch (Exception e) {
            log.error("IPN通知发生IO异常" + e.getMessage());
            log.error("Class: " + this.getClass().getName() + " method: " + Thread.currentThread().getStackTrace()[1].getMethodName());
            out.println("confirmError");
            e.printStackTrace();
        }
        out.flush();
        out.close();
        log.info("=========================================================================================");
    }

    /**
     * 查看已付款账单的状态
     */
    @RequestMapping(method = RequestMethod.GET, value = "test")
    @ResponseBody
    public String selectTransactionState(@RequestParam("paymentId") String paymentId) {
        log.info("=========================================================================================");
        String state = "未产生支付信息";
        String custom = "";
        try {
            Payment payment = Payment.get(apiContext, paymentId);
            if (payment.getTransactions().size() > 0 && payment.getTransactions().get(0).getRelatedResources().size() > 0) {
                if (payment.getIntent().equals(PaypalPaymentIntent.sale.toString())) {// 交易订单
                    state = payment.getTransactions().get(0).getRelatedResources().get(0).getSale().getState();
                    custom = payment.getTransactions().get(0).getCustom();
                } else if (payment.getIntent().equals(PaypalPaymentIntent.authorize.toString())) {// 授权订单
                    state = payment.getTransactions().get(0).getRelatedResources().get(0).getAuthorization().getState();
                    custom = payment.getTransactions().get(0).getCustom();
                } else if (payment.getIntent().equals(PaypalPaymentIntent.order.toString())) {// 授权订单
                    state = payment.getTransactions().get(0).getRelatedResources().get(0).getOrder().getState();
                    custom = payment.getTransactions().get(0).getCustom();
                }
            }
        } catch (PayPalRESTException e) {
            e.printStackTrace();
        }
        log.info("订单状态：{} ", state);
        log.info(custom);
        log.info("=========================================================================================");
        return state;
    }
}